package fi.iki.joker.mobilogger;

import java.sql.Date;
import java.util.Map;

import android.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
import android.text.Selection;
import android.text.Spannable;
import android.text.style.DynamicDrawableSpan;
import android.text.style.StyleSpan;
import android.text.style.URLSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.widget.EditText;
import fi.iki.joker.mobilogger.db.BlogConfigBean;
import fi.iki.joker.mobilogger.db.BlogEntryBean;
import fi.iki.joker.mobilogger.db.DBUtil;
import fi.iki.joker.mobilogger.editor.HTMLEmbedSpan;
import fi.iki.joker.mobilogger.editor.ImageEmbedSpan;
import fi.iki.joker.mobilogger.editor.SpannableBufferHelper;
import fi.iki.joker.mobilogger.util.AlertUtil;

/**
 * This class is the main editor for blog entries. It does a lot (maybe too much), for example in the
 * future it would be good to delegate the rich text editing functionality to an external class.
 * @author juhak
 */

public class BlogEntryEditor extends Activity {

	private static final String TAG = "BlogEntryEditor";
	private static final int PUBLISH_REQUEST = 1;
	// The order array is used to map which blog config has which 
	// index in the list of blog configs. This is then used in the
	// preview activity to preset the selection spinner there.
	private int [] order = null;
	private BlogEntryBean b = null;
	private static final int STATE_INSERT = 0;
	private static final int STATE_EDIT = 1;
	private static final int STATE_DELETE = 2;
	private static final int GROUP_BASIC = 0;
	private static final int GROUP_PUBLISH = 1;
	private static final int GROUP_EMBED = 2;
	private static final int NON_SELECTABLE_GROUP_ID = 65535;
	private static final int REQUEST_CODE_IMAGECHOOSER = 222;
	private int mState = 0;
	private Uri mURI = null;
	private SpannableBufferHelper helper = null;
	private URLSpan currentUrlSpan = null;
	private int currentSpanStart = 0;
	private int currentSpanEnd = 0;
	private DynamicDrawableSpan currentHtmlSpan = null;
	private EditText myText;
	private String myString;
	
	public static class BlogText extends EditText {
        private Rect mRect;
        private Paint mPaint;

        // we need this constructor for ViewInflate
        public BlogText(Context context, AttributeSet attrs) {
            super(context, attrs);

            mRect = new Rect();
            mPaint = new Paint();
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setColor(0xFF0000FF);
        }

        @Override
        protected void onDraw(Canvas canvas) {

            int count = getLineCount();
            Rect r = mRect;
            Paint paint = mPaint;

            for (int i = 0; i < count; i++) {
                int baseline = getLineBounds(i, r);

                canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1,
                                paint);
            }

            super.onDraw(canvas);
        }
    }
	
	public static class TitleText extends EditText {
        private Rect mRect;
        private Paint mPaint;

        // we need this constructor for ViewInflate
        public TitleText(Context context, AttributeSet attrs) {
            super(context, attrs);

            mRect = new Rect();
            mPaint = new Paint();
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setColor(0xFF0000FF);
        }

        @Override
        protected void onDraw(Canvas canvas) {

            int count = getLineCount();
            Rect r = mRect;
            Paint paint = mPaint;

            for (int i = 0; i < count; i++) {
                int baseline = getLineBounds(i, r);

                canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1,
                                paint);
            }

            super.onDraw(canvas);
        }
    }
	
	public static class UrlText extends EditText {
        private Rect mRect;
        private Paint mPaint;

        // we need this constructor for ViewInflate
        public UrlText(Context context, AttributeSet attrs) {
            super(context, attrs);

            mRect = new Rect();
            mPaint = new Paint();
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setColor(0xFF0000FF);
        }
        public UrlText(Context context) {
            super(context);

            mRect = new Rect();
            mPaint = new Paint();
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setColor(0xFF0000FF);
        }
        

        @Override
        protected void onDraw(Canvas canvas) {

            int count = getLineCount();
            Rect r = mRect;
            Paint paint = mPaint;

            for (int i = 0; i < count; i++) {
                int baseline = getLineBounds(i, r);

                canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1,
                                paint);
            }

            super.onDraw(canvas);
        }
    }
	
	@Override
	protected void onCreate(Bundle icicle) {
		// TODO Auto-generated method stub
		super.onCreate(icicle);
		setContentView(fi.iki.joker.mobilogger.R.layout.blogentryeditor);
		helper = new SpannableBufferHelper();
		Intent intent = this.getIntent();
		if(intent.getAction().equals(Intent.ACTION_INSERT)) {
			mState = STATE_INSERT;
			b = new BlogEntryBean();
		} else if(intent.getAction().equals(Intent.ACTION_EDIT)) {
			mState = STATE_EDIT;
			mURI = intent.getData();
			int configid = -1;
        	String id = mURI.getPath();
        	String temp = null;
        	if(id != null) {
        		try {
        			temp = id.substring(id.lastIndexOf('/')+1);
        			configid = Integer.parseInt(temp);
        		} catch (NumberFormatException ne) {
        			Log.e(TAG, "Unable to parse the Intent path id ("+temp+") into an integer!");
        		}
        	}
        	if(configid > 0) {
        		DBUtil conn = new DBUtil();
   	        	Log.d(TAG, "DB ready, fetching blog entry");
   	        	b = conn.getBlogEntryById(this,configid);
        	}
		} else if(intent.getAction().equals(Intent.ACTION_DELETE)) {
			mState = STATE_DELETE;
		}
		EditText blogeditor = (EditText)findViewById(fi.iki.joker.mobilogger.R.id.blog);
		EditText blogtitle = (EditText)findViewById(fi.iki.joker.mobilogger.R.id.blogtitle);
		blogeditor.setText(b.getBlogEntry());
		blogtitle.setText(b.getTitle());
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		int id = item.getItemId();
		int group = item.getGroupId();
		EditText blogeditor = (EditText)findViewById(fi.iki.joker.mobilogger.R.id.blog);
		helper.debugWriteBuffer("Buffer before changes:", blogeditor.getText());
		EditText blogtitle = (EditText)findViewById(fi.iki.joker.mobilogger.R.id.blogtitle);
		Spannable text = blogeditor.getText();
		Spannable title = blogtitle.getText();
		if(b == null) {
			b = new BlogEntryBean();
		}
		b.setBlogEntry(text);
		b.setTitle(title.toString());
		b.setCreated(new Date(System.currentTimeMillis()));
		// Then handle the text formatting options
		int selectionStart = Selection.getSelectionStart(text);
		int selectionEnd = Selection.getSelectionEnd(text);
		// if user has selected from end to beginning, flip
		// values, we are interested about the selection, not
		// direction
		if(selectionStart > selectionEnd) {
			int temp = selectionEnd;
			selectionEnd = selectionStart;
			selectionStart = temp;
		}
		// menu options for handling saving and formatting.
		if(group == GROUP_BASIC) {
			if(id == Menu.FIRST) {
				if(!saveEntryLocally(b)) {
					Log.e(TAG, "Failed to save the entry.");
					AlertUtil.showAlert(this,"Failed to save the entry","Failed to save the entry, check that your device has enough free memory.");
				}
			}
			if((id != Menu.FIRST) && !blogeditor.hasFocus()) {
				AlertUtil.showAlert(this,"Not available","Only the blog text can have styling information, sorry.");
				return super.onOptionsItemSelected(item);
			}
			if(selectionStart == selectionEnd) {
				switch(id) {
				case 3:
				text.setSpan(new StyleSpan(Typeface.BOLD),
						selectionStart,selectionEnd,Spannable.SPAN_INCLUSIVE_INCLUSIVE);
				blogeditor.setText(text);
				blogeditor.setSelection(selectionStart);
				break;
				case 4:
					text.setSpan(new StyleSpan(Typeface.ITALIC),
							selectionStart,selectionEnd,Spannable.SPAN_INCLUSIVE_INCLUSIVE);
					blogeditor.setText(text);
				blogeditor.setSelection(selectionStart);
				break;
				case 5:
					text.setSpan(new StyleSpan(Typeface.NORMAL),
							selectionStart,selectionEnd,Spannable.SPAN_INCLUSIVE_INCLUSIVE);
					blogeditor.setText(text);
				blogeditor.setSelection(selectionStart);
				}
			} else {
				switch (id) {
				case 3:
					text.setSpan(new StyleSpan(Typeface.BOLD),
						selectionStart,selectionEnd,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
					blogeditor.setText(text);
					blogeditor.setSelection(selectionEnd);
					break;
				case 4 :
					text.setSpan(new StyleSpan(Typeface.ITALIC),
						selectionStart,selectionEnd,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
					blogeditor.setText(text);
					blogeditor.setSelection(selectionEnd);
					break;
				case 5 :
					text.setSpan(new StyleSpan(Typeface.NORMAL),
						selectionStart,selectionEnd,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
					blogeditor.setText(text);
					blogeditor.setSelection(selectionEnd);
					break;
				}
			}
		// this group of menu options handles object embedding
		} else if(group == GROUP_EMBED) {
			if((id > 6 && id < 12) && !blogeditor.hasFocus()) {
				AlertUtil.showAlert(this,"Not available","Only the blog text can have embedded objects, sorry.");
				return super.onOptionsItemSelected(item);
			}
			if(id == 7) { //LINK 
				currentUrlSpan = new URLSpan("");
				currentSpanStart = selectionStart;
				currentSpanEnd = selectionEnd;
				getTextInputFromDialog();
			} else if(id == 8) { //HTML
				if(selectionStart == selectionEnd) {
					currentSpanStart = selectionStart;
					currentSpanEnd = selectionStart;
					getHtmlInputFromDialog();
				} else {
					AlertUtil.showAlert(this,"Invalid selection","When embedding HTML markup, don't select a range.");
					return super.onOptionsItemSelected(item);
				}
			} else if(id == 9) { //Audio
				AlertUtil.showAlert(this,"Not supported yet.", "Embedding Audio to Blog entry is not yet supported.");
			} else if(id == 10) { //Image
				if(selectionStart == selectionEnd) {
					currentSpanStart = selectionStart;
					currentSpanEnd = selectionStart;
					getImageSelectionFromActivity();
				} else {
					AlertUtil.showAlert(this,"Invalid selection","When embedding an image, don't select a range.");
					return super.onOptionsItemSelected(item);
				}
			} else if(id == 11) { //Video
				AlertUtil.showAlert(this,"Not supported yet.","Embedding Video to Blog entry is not yet supported.");
			}
		} else if(group == GROUP_PUBLISH) {
			if(id == 65535) {
				return super.onOptionsItemSelected(item);
			}
			if(!saveEntryLocally(b)) {
				Log.e(TAG, "Failed to save the entry.");
				AlertUtil.showAlert(this,"Failed to save the entry", "Failed to save the entry, check that your device has enough free memory.");
			}
			int configId = order[id];
			Log.d(TAG, "Calling to startPreviewDialog with configId"+configId);
			b.setPublishedIn(configId);
			startPreviewDialog(configId,b);
		}
		helper.debugWriteBuffer("Buffer after changes:", blogeditor.getText());
		Log.d(TAG,"As XHTML:"+helper.SpannableToXHTML(blogeditor.getText()));
		return super.onOptionsItemSelected(item);
	}
	
    public boolean onCreateOptionsMenu (Menu menu) {
		boolean result = super.onCreateOptionsMenu(menu);
		// Create first level
		menu.add(GROUP_BASIC, Menu.FIRST, Menu.NONE,fi.iki.joker.mobilogger.R.string.blogedit_save);
		SubMenu sub = menu.addSubMenu(GROUP_PUBLISH, NON_SELECTABLE_GROUP_ID,  Menu.NONE,fi.iki.joker.mobilogger.R.string.blogedit_publish);
		menu.add(GROUP_BASIC, 3,  Menu.NONE,fi.iki.joker.mobilogger.R.string.blogedit_bold);
		menu.add(GROUP_BASIC, 4,  Menu.NONE,fi.iki.joker.mobilogger.R.string.blogedit_italic);
		menu.add(GROUP_BASIC, 5,  Menu.NONE,fi.iki.joker.mobilogger.R.string.blogedit_normal);
		SubMenu embed = menu.addSubMenu(GROUP_EMBED, 6,  Menu.NONE,fi.iki.joker.mobilogger.R.string.blogedit_embed);
		embed.add(GROUP_EMBED, 7,  Menu.NONE,fi.iki.joker.mobilogger.R.string.blogedit_embed_link);
		embed.add(GROUP_EMBED, 8,  Menu.NONE,fi.iki.joker.mobilogger.R.string.blogedit_embed_html);
		embed.add(GROUP_EMBED, 9,  Menu.NONE,fi.iki.joker.mobilogger.R.string.blogedit_embed_audio);
		embed.add(GROUP_EMBED, 10,  Menu.NONE,fi.iki.joker.mobilogger.R.string.blogedit_embed_image);
		embed.add(GROUP_EMBED, 11,  Menu.NONE,fi.iki.joker.mobilogger.R.string.blogedit_embed_video);
		// Create submenu based on the available blogs configured
		DBUtil db = new DBUtil();
		BlogConfigBean[] bcbs = null;

		bcbs = db.getConfiguredBlogs(this);
		if(bcbs == null) {
			// Having no blogs may not be error as initially 
			// there is no blogs configured. Empty submenu in this case.
			Log.d(TAG,"No blogs configured while creating publish submenu...");
			AlertUtil.showAlert(this,"No blogs configured.",
					"You have not configured any blogs to MobiLogger. This is ok, "+
					"but you can only then save blog entries locally to your device."+
					"To configure blogs, you need to go to MobiLogger main menu and "+
					"select the option \"New blog configuration\".");
			sub.add(GROUP_PUBLISH,NON_SELECTABLE_GROUP_ID,SubMenu.NONE, "[no blogs]");
		} else {
			order = new int[bcbs.length];
			//we got blogs configured in DB so let's put those in menu.
			for(int i = 0; i < bcbs.length; i++) {
				int id = bcbs[i].getId();
				order[i]=bcbs[i].getId();
				Log.d(TAG,"Create order: ["+i+"] -> "+bcbs[i].getId());
				String blogname = bcbs[i].getBlogname();
				sub.add(GROUP_PUBLISH, i,SubMenu.NONE, blogname);
			}
		}
		return result; 
	}
    
    /**
     * Gets a result back from subactivity
     */
    
    @Override
	protected void onActivityResult(int requestCode, int resultCode,
			Intent intent) {
		
		if(resultCode == RESULT_CANCELED) {
			return;
		} else if(resultCode == RESULT_OK) {
			if(requestCode == REQUEST_CODE_IMAGECHOOSER) {
				// user exited ImageSelectionActivity
				imageChosen("");
			}
		}
    	//super.onActivityResult(requestCode, resultCode, data, extras);
	}
    
    /**
     * Saves the blog entry locally to DB.
     * @param b
     * @return
     */
    
    private boolean saveEntryLocally(BlogEntryBean b) {
    	DBUtil db = new DBUtil();
    	//save text as xhtml
    	if(mState == STATE_INSERT) {
    		long insertResult = db.insertAndReturnRow(this,b);
    		if(insertResult != -1) {
    			Log.d(TAG,"Inserted blog entry successfully, return true.");
    			Log.d(TAG,"Changing to edit mode...");
    			mState = STATE_EDIT;
    			// may fail for real bloggers
    			this.b.setId((int)insertResult);
    			return true;
    		} else {
    			Log.e(TAG,"DBUtil reported false from insert(BlogEntryBean)!!!");
    			AlertUtil.showAlert(this,"Save failed!","Saving the entry locally failed due to a database error.");
    			return false;
    		}
    		//TODO:the error handling here is not consistent
    	} else if(mState == STATE_EDIT){
    		Log.d(TAG,"Updating blog entry "+b.getId()+" ...");
    		db.update(this,b);
    		return true;
    	}
    	return true;
    }
    
    /**
     * Starts a preview activity for the to-be published blog entry
     * @param publish_to
     * @param b
     */
    
    private void startPreviewDialog(int publish_to, BlogEntryBean b) {
    	//to the preview activity we send the id of the 
    	//entry we're going to publish
    	//and the id of the config used to publish this
    	Intent i = new Intent(this, PreviewDialog.class);
    	String URIStr = "content://fi.iki.joker.mobilogger/blogentry/"+b.getId();
		i.setData(Uri.parse(URIStr));
		i.putExtra("fi.iki.joker.mobilogger.PublishConfigID", publish_to);
		i.setAction(Intent.ACTION_INSERT);
		this.startActivityForResult(i,this.PUBLISH_REQUEST);
    }
    
    private void getImageSelectionFromActivity() {
    	Intent i = new Intent(this, ImageSelectionActivity.class);
    	this.startActivityForResult(i, REQUEST_CODE_IMAGECHOOSER);
    }
    
    private void getTextInputFromDialog() {
    	AlertDialog.Builder dialog = new AlertDialog.Builder(this);
    	myText = new UrlText(this);
    	dialog.setView(myText);
    	dialog.setTitle("Enter URL");
    	dialog.setPositiveButton("OK", okButtonListener);
    	dialog.setCancelable(false);
    	dialog.show();
    }
    
    private void getHtmlInputFromDialog() {
    	AlertDialog.Builder dialog = new AlertDialog.Builder(this);
    	myText = new UrlText(this);
    	dialog.setView(myText);
    	dialog.setTitle("Enter HTML");
    	dialog.setPositiveButton("OK", okHtmlListener);
    	dialog.setCancelable(false);
    	dialog.show();
    }
	
    /**
     * This listener handles the button click from the 
     * dialog where user is able to enter an URL to embed.
     * Takes the url from dialog and adds it to the spans
     * of the blogeditor.
     */
    
    OnClickListener okButtonListener = new OnClickListener() {
        public void onClick(DialogInterface df, int item) {
        	EditText blogeditor = (EditText)findViewById(fi.iki.joker.mobilogger.R.id.blog);
        	Spannable text = blogeditor.getText();
        	myString = myText.getText().toString();
        		if(currentSpanStart == currentSpanEnd) {
        			//in the case that the span's length is zero, we insert the links url to
        			//be the text content of the link
        			String fixed = " "+myString+" ";
        			blogeditor.getText().insert(currentSpanStart, fixed);
        			blogeditor.getText().setSpan(new URLSpan(fixed),currentSpanStart+1,currentSpanStart+fixed.length()-1,
        			 	Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        		} else {	
        			text.setSpan(new URLSpan(myString),
        				currentSpanStart, currentSpanEnd,
        				Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        			blogeditor.setText(text);
        			blogeditor.setSelection(currentSpanEnd);
        		}
        }
    };
    
    /**
     * This listener handles the button click from the 
     * dialog where user is able to enter HTML markup to embed.
     * Takes the html from dialog and adds it to the spans
     * of the blogeditor.
     */
    
    OnClickListener okHtmlListener = new OnClickListener() {
        public void onClick(DialogInterface df, int item) {
        	EditText blogeditor = (EditText)findViewById(fi.iki.joker.mobilogger.R.id.blog);	
        	myString = myText.getText().toString();
        	//append one empty space which will then be spanned 
        	//by the HTMLEmbedSpan.
        	//fixme, there something wrong here still
        	blogeditor.getText().insert(currentSpanStart," ");
        	Spannable text = blogeditor.getText(); 
        	text.setSpan(new HTMLEmbedSpan(myString, getApplication()),
        	currentSpanStart, currentSpanEnd+1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        	blogeditor.setText(text);
			blogeditor.setSelection(currentSpanStart);
        }
    };
	
    OnCancelListener cancelListener = new OnCancelListener() {
    	
    	public void onCancel(DialogInterface arg0) {
			// TODO Auto-generated method stub
			
		}
    };
    
    /**
     * This handles the image selection. It's similar to dialog
     * listeners, except that the ImageSelectionActivity is an activity.
     * @param imgUrl
     */
    
    private void imageChosen(String imgUrl) {
    	EditText blogeditor = (EditText)findViewById(fi.iki.joker.mobilogger.R.id.blog);	
    	blogeditor.getText().insert(currentSpanStart," ");
    	Spannable text = blogeditor.getText(); 
    	text.setSpan(new ImageEmbedSpan(imgUrl, getApplication()),
    	currentSpanStart, currentSpanEnd+1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    	blogeditor.setText(text);
    }

}
